Python: Add tool concurrency groups and sequential execution order for same-message calls - #7523
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds framework-level controls to prevent same-message tool-call race conditions in the Python core by allowing (a) per-tool serialization via concurrency groups and (b) run-level sequential execution of all tool calls in a batch.
Changes:
- Added
concurrency_group: str | NonetoFunctionTooland the@tooldecorator, enabling sequential execution for tools sharing a group within a single message batch. - Added
tool_execution_order: Literal["parallel","sequential"]to chat options and function invocation configuration, and wired it through to the function-call execution layer. - Added unit tests covering concurrency-group serialization and sequential execution behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_tools.py | Adds concurrency-group metadata, serializes it, and implements grouped/sequential function-call execution and option wiring. |
| python/packages/core/agent_framework/_types.py | Extends chat options with tool_execution_order to expose the configuration at the API surface. |
| python/packages/core/tests/core/test_tools.py | Adds tests validating concurrency-group handling and sequential tool execution order. |
| # Bind one executor with the run's custom arguments, middleware, configuration, and session. | ||
| options = dict(options) if options else {} | ||
|
|
||
| if tool_exec_order := options.pop("tool_execution_order", None): |
There was a problem hiding this comment.
Can we please keep tool_execution_order out of additional_function_arguments? Agent.as_tool() forwards FunctionInvocationContext.kwargs into the nested agent's function_invocation_kwargs, and _execute_function_calls gives that value precedence over the nested agent's own configuration. An outer run explicitly using parallel can therefore override a nested agent configured for sequential, causing its stateful tools to race; filtering this internal key or carrying it only in config would preserve the nested boundary.
There was a problem hiding this comment.
I've updated the code to keep tool_execution_order strictly within the run-level config instead. It's now extracted from options and merged into a copy of self. function_invocation_configuration and passes only via config parameter to _execute _function_call
Motivation & Context
Currently, the framework executes all tool calls requested in a single assistant message concurrently. While this is a great default for independent calls (like parallel document lookups), models routinely emit dependent calls in one batch (e.g., "write the file, then read it"). Because the tool author has no way to serialize these calls, dependent reads race the still-running writes, leading to "not found" errors and contradictory agent states.
This PR closes that gap by providing declarative, framework-level control over tool execution order, preventing stateful tool race conditions without relying on fragile, tool-side
asyncio.Lockworkarounds.Fixes #7386
Description & Review Guide
What are the major changes?
concurrency_group: strparameter toFunctionTooland the@tooldecorator. Tools sharing the sameconcurrency_groupexecute sequentially in call order within a message batch, while ungrouped tools remain fully concurrent.tool_execution_order: Literal["parallel", "sequential"]to_ChatOptionsBaseandFunctionInvocationConfiguration. Setting this to"sequential"forces all tool calls in a batch to execute one-by-one.tool_execution_orderchat option through theFunctionInvocationLayerdown to the execution engine so the setting actually takes effect at runtime.FunctionTool.to_dict()to ensureconcurrency_groupsurvives serialization, and added docstrings documenting the ordering guarantee (specifically requested in the issue).What is the impact of these changes?
This is fully backward compatible. The default behavior remains
"parallel"with noconcurrency_groupset, ensuring existing agents behave exactly as before. It provides tool authors a safe, declarative way to handle stateful dependencies.What do you want reviewers to focus on?
Please review the grouping algorithm in
_try_execute_function_call_groups(_tools.py). Specifically, verify that theordered_resultsarray correctly maps indices to ensure results are returned in the exact order the model requested them, and thatcontextvars.copy_context()is still applied correctly per-call to preserve agent span observability.Related Issue
Fixes #7386
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.